package fr.sedoo.metadata.client.ui.editablechosen;

import static com.google.gwt.query.client.GQuery.$;
import static com.google.gwt.query.client.GQuery.document;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.dom.client.StyleInjector;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.js.JsObjectArray;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Event;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.watopi.chosen.client.ChosenOptions;
import com.watopi.chosen.client.event.UpdatedEvent;
import com.watopi.chosen.client.resources.ChozenCss;
import com.watopi.chosen.client.resources.Resources;

import fr.sedoo.metadata.client.ui.editablechosen.SelectParser.GroupItem;
import fr.sedoo.metadata.client.ui.editablechosen.SelectParser.OptionItem;
import fr.sedoo.metadata.client.ui.editablechosen.SelectParser.SelectItem;
import fr.sedoo.metadata.client.ui.editablechosen.event.FreeTextChosenChangeEvent;
import fr.sedoo.metadata.client.ui.editablechosen.event.FreeTextChosenEvent;
import fr.sedoo.metadata.client.ui.editablechosen.event.HidingDropDownEvent;
import fr.sedoo.metadata.client.ui.editablechosen.event.MaxSelectedEvent;
import fr.sedoo.metadata.client.ui.editablechosen.event.NewValueEvent;
import fr.sedoo.metadata.client.ui.editablechosen.event.ReadyEvent;

public class FreeTextChosenImpl {

	public static interface ChozenTemplate extends SafeHtmlTemplates {
		public ChozenTemplate templates = GWT.create(ChozenTemplate.class);

		@Template("<li class=\"{1}\" id=\"{0}\"><span>{2}</span><a href=\"javascript:void(0)\" class=\"{3}\" rel=\"{4}\"></a></li>")
		SafeHtml choice(String id, String searchChoiceClass, String content, String searchChoiceCloseClass, String rel);

		@Template("<div id=\"{0}\" class=\"{1}\"></div>")
		SafeHtml container(String id, String cssClasses);

		@Template("<ul class=\"{0}\"><li class=\"{1}\"><input type=\"text\" value=\"{2}\" class=\"{3}\" autocomplete=\"off\" style=\"width:25px;\"/></li></ul><div class=\"{4}\" style=\"left:-9000px;\"><ul class=\"{5}\"></ul></div>")
		SafeHtml contentMultiple(String chznChoicesClass, String chznSearchFieldClass, String defaultText, String defaultClass, String chznDropClass, String chznResultClass);

		@Template("<a href=\"javascript:void(0)\" class=\"{0} {1}\"><span>{2}</span><div><b></b></div></a><div class=\"{3}\" style=\"left:-9000px;\"><div class=\"{4}\"><input type=\"text\" autocomplete=\"off\" /></div><ul class=\"{5}\"></ul></div>")
		SafeHtml contentSingle(String chznSingleClass, String chznDefaultClass, String defaultText, String dropClass, String chznSearchClass, String chznResultClass);

		@Template("<li id=\"{0}\" class=\"{1}\">{2}</li>")
		SafeHtml group(String id, String groupResultClass, String content);

		@Template("<li class=\"{0}\">{1}\"<span></span>\"</li>")
		SafeHtml noResults(String noResultsClass, String content);

		@Template("<li id=\"{0}\" class=\"{1}\" style=\"{2}\">{3}</li>")
		SafeHtml option(String id, String groupResultClass, String style, String content);

	}

	private static final RegExp containerIdRegExp = RegExp.compile("[^\\w]", "g");
	private static boolean cssInjected = false;

	private static final String DEFAULT_CONTAINER_ID = "chozen_container__";
	private static int idCounter = 0;
	private static final RegExp regExpChars = RegExp.compile("[-[\\]{}()*+?.,\\\\^$|#\\s]", "g");
	private static final String TABINDEX_PROPERTY = "tabindex";

	private GQuery $selectElement;
	private Function activateAction;
	private boolean activeField = false;
	private boolean allowSingleDeselect = false;
	private int backstrokeLength;
	private int choices;
	private Function clickTestAction;
	private GQuery container;
	private String containerId;
	private ChozenCss css;
	private String currentValue;
	private String defaultText;
	private GQuery dropdown;
	private EventBus eventBus;
	private int fWidth;
	private boolean isDisabled;
	private boolean isMultiple;
	private boolean isRTL;
	private boolean mouseOnContainer = false;
	private ChosenOptions options;
	private GQuery pendingBackstroke;
	private boolean pendingDestroyClick;
	private GQuery resultHighlight;
	private GQuery resultSingleSelected;
	private String resultsNoneFound;
	private boolean resultsShowing = false;
	private GQuery searchChoices;
	private GQuery searchContainer;
	private GQuery searchField;
	private GQuery searchResults;
	private GQuery selectedItem;
	private SelectElement selectElement;
	private JsObjectArray<SelectItem> selectItems;
	private HandlerRegistration updateEventHandlerRegistration;

	public GQuery getContainer() {
		return container;
	}

	public String getCurrentValue() {
		return currentValue;
	}

	public ChosenOptions getOptions() {
		return options;
	}

	public SelectElement getSelectElement() {
		return selectElement;
	}

	/**
	 * Is the plugin support the current broxser ?
	 * 
	 * @return
	 */
	public boolean isSupported() {
		return true;
	}

	protected void init(SelectElement element, ChosenOptions options, EventBus eventBus) {
		this.selectElement = element;
		this.options = options;
		this.eventBus = eventBus;

		this.$selectElement = $(selectElement);

		setDefaultValues();

		this.isMultiple = selectElement.isMultiple();

		setDefaultText();

		setup();

		bind();

		finishSetup();

	}

	protected void update() {
		if (!isMultiple) {
			resultsResetCleanup();
		}

		resultClearHighlight();
		resultSingleSelected = null;
		resultsBuild();
	}

	protected void release() {
		if (updateEventHandlerRegistration != null) {
			updateEventHandlerRegistration.removeHandler();
			updateEventHandlerRegistration = null;
		}
		// empty the searchResult to speed up the container.remove()
		if (searchResults != null) {
			searchResults.html("");
		}

		// remove method clean listener and cie
		container.remove();

		$selectElement.removeClass(css.chznDone(), "chzn-done").show();
	}

	private boolean activateField(Event e) {
		if (!isMultiple && !activeField) {
			searchField.attr(TABINDEX_PROPERTY, selectedItem.attr(TABINDEX_PROPERTY));
			selectedItem.attr(TABINDEX_PROPERTY, -1);
		}

		container.addClass(css.chznContainerActive());
		activeField = true;

		searchField.val(searchField.val());
		searchField.focus();
		return false;
	}

	private void bind() {
		container.mousedown(new Function() {
			@Override
			public boolean f(Event e) {
				return containerMouseDown(e);

			}

		});

		container.mouseup(new Function() {
			@Override
			public boolean f(Event e) {
				return containerMouseUp(e);
			}

		});

		container.mouseenter(new Function() {
			@Override
			public boolean f(Event e) {
				mouseOnContainer = true;
				return false;
			}
		});

		container.mouseleave(new Function() {
			@Override
			public boolean f(Event e) {
				mouseOnContainer = false;
				return false;
			}
		});

		if (eventBus != null) {
			updateEventHandlerRegistration = eventBus.addHandler(UpdatedEvent.getType(), new UpdatedEvent.UpdatedHandler() {
				public void onUpdated(UpdatedEvent event) {
					update();
				}
			});
		}

		searchField.blur(new Function() {
			@Override
			public boolean f(Event e) {

				/*
				 * On appelle sendNewValueEvent pour qu'un eventuel mot, en
				 * train d'être saisi soit validé
				 */
				sendNewValueEvent();

				return inputBlur(e);
			}
		});

		searchField.keyup(new Function() {
			@Override
			public boolean f(Event e) {
				return keyupChecker(e);
			}

		});

		searchField.keydown(new Function() {
			@Override
			public boolean f(Event e) {
				return keydownChecker(e);
			}

		});

		if (isMultiple) {
			searchChoices.click(new Function() {
				@Override
				public boolean f(Event e) {
					return choicesClick(e);
				}

			});
			searchField.focus(new Function() {
				@Override
				public boolean f(Event e) {
					return inputFocus(e);

				}

			});

		} else {
			container.click(new Function() {
				@Override
				public boolean f(Event e) {
					e.preventDefault();
					return false;
				}
			});
		}
	}

	private void blurTest() {
		if (!activeField && container.hasClass(css.chznContainerActive())) {
			closeField();
		}
	}

	private String buildContainerId() {
		String id = null;
		String selectElementId = selectElement.getId();

		if (selectElementId != null && selectElementId.length() > 0) {
			id = containerIdRegExp.replace(selectElementId, "_");
		} else {
			id = generateContainerId();
			selectElement.setId(id);
		}

		id += "_chzn";

		return id;
	}

	private void choiceBuild(OptionItem option) {
		if (isMultiple && maxSelectedOptionsReached()) {
			fireEvent(new MaxSelectedEvent(this));
			return;
		}

		String choiceId = containerId + "_c_" + option.getArrayIndex();
		choices++;
		searchContainer.before(ChozenTemplate.templates.choice(choiceId, css.searchChoice(), option.getHtml(), css.searchChoiceClose(), "" + option.getArrayIndex()).asString());
		$('#' + choiceId).find("a").click(new Function() {
			public boolean f(final Event e) {
				choiceDestroyLinkClick(e);
				return false;
			}

		});
	}

	private void choiceDestroy(GQuery link) {
		choices--;
		showSearchFieldDefault();
		if (isMultiple && choices > 0 && searchField.val() != null && searchField.val().length() < 1) {
			resultsHide();
		}

		resultDeselect(Integer.parseInt(link.attr("rel")));
		link.parents("li").first().remove();
	}

	private void choiceDestroyLinkClick(Event e) {
		e.preventDefault();
		if (!isDisabled) {
			pendingDestroyClick = true;
			Element target = e.getEventTarget().cast();
			choiceDestroy($(target));
		} else {
			e.stopPropagation();
		}

	}

	private boolean choicesClick(Event e) {
		e.preventDefault();

		Element target = e.getEventTarget().cast();
		GQuery $e = $(target);

		if (activeField && !($e.hasClass(css.searchChoice()) || !$e.parents("." + css.searchChoice()).isEmpty()) && !resultsShowing) {
			resultsShow();
		}
		return false;
	}

	private void clearBackstroke() {
		if (pendingBackstroke != null) {
			pendingBackstroke.removeClass(css.searchChoiceFocus());
		}
		pendingBackstroke = null;

	}

	private void closeField() {
		$(document).unbind("click", clickTestAction);

		if (!isMultiple) {
			selectedItem.attr(TABINDEX_PROPERTY, searchField.attr(TABINDEX_PROPERTY));
			searchField.attr(TABINDEX_PROPERTY, "-1");
		}

		activeField = false;

		resultsHide();

		container.removeClass(css.chznContainerActive());
		winnowResultsClear();
		clearBackstroke();

		showSearchFieldDefault();
		searchFieldScale();

	}

	private boolean containerMouseDown(Event e) {
		if (isDisabled) {
			return true;
		}
		Element target = e.getEventTarget().cast();
		GQuery $e = $(target);

		boolean targetCloseLink = $e.hasClass(css.searchChoiceClose());

		if (!resultsShowing) {
			e.stopPropagation();
		}

		if (!pendingDestroyClick && !targetCloseLink) {
			if (!activeField) {
				if (isMultiple) {
					searchField.val("");
				}
				$(document).click(clickTestAction);
				resultsShow();
			} else if (!isMultiple && !$e.isEmpty() && ($e.get(0) == selectedItem.get(0) || $e.parents("a." + css.chznSingle()).length() > 0)) {
				e.preventDefault();
				resultsToggle();
			}

			activateField(e);

		} else {
			pendingDestroyClick = false;
		}

		return false;
	}

	private boolean containerMouseUp(Event e) {
		Element target = e.getEventTarget().cast();
		GQuery $e = $(target);

		if (!$e.isEmpty() && "ABBR".equalsIgnoreCase($e.get(0).getNodeName()) && !isDisabled) {
			resultsReset(e);
			return false;
		}

		return true;
	}

	private void finishSetup() {
		$selectElement.addClass(css.chznDone(), "chzn-done");

	}

	private void fireEvent(FreeTextChosenEvent<?> event) {
		if (eventBus != null) {
			eventBus.fireEvent(event);
		}

	}

	private String generateContainerId() {
		String id = DEFAULT_CONTAINER_ID + idCounter++;

		while (!$("#" + id).isEmpty()) {
			idCounter++;
			id = DEFAULT_CONTAINER_ID + idCounter;
		}
		return id;
	}

	private int getSideBorderPadding(GQuery elmt, boolean isHidden) {
		if (isHidden) {
			// bug in gquery when one parent of the element is hidden
			return (int) (elmt.cur("padding-left", true) + elmt.cur("padding-right", true) + elmt.cur("border-left-width", true) + elmt.cur("border-right-width", true));
		}
		return elmt.outerWidth() - elmt.width();
	}

	private boolean inputBlur(Event e) {
		if (!mouseOnContainer) {
			activeField = false;
			Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
				public boolean execute() {
					blurTest();
					return false;
				}
			}, 100);
		}
		return false;
	}

	private boolean inputFocus(final Event e) {
		if (!activeField) {
			Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
				public boolean execute() {
					containerMouseDown(e);
					return false;
				}
			}, 50);
		}
		return false;
	}

	private void keydownBackstroke() {
		if (pendingBackstroke != null) {
			choiceDestroy(pendingBackstroke.find("a").first());
			clearBackstroke();
		} else {
			pendingBackstroke = searchContainer.siblings("li." + css.searchChoice()).last();
			if (options.isSingleBackstrokeDelete()) {
				keydownBackstroke();
			} else {
				pendingBackstroke.addClass(css.searchChoiceFocus());
			}
		}

	}

	private boolean keydownChecker(Event e) {
		int stroke = e.getKeyCode();
		searchFieldScale();

		if (stroke != 8 && pendingBackstroke != null) {
			clearBackstroke();
		}

		switch (stroke) {
		case 8: // backspace
			backstrokeLength = searchField.val().length();
			break;

		case 9: // tab
			if (resultsShowing && !isMultiple) {
				resultSelect(e);
			}
			mouseOnContainer = false;
			break;

		case 13: // enter
			e.preventDefault();
			return false;

		case 38: // up arrow
			e.preventDefault();
			// keyupArrow();
			return false;

		case 40: // down arrow
			// this.keydownArrow();
			return false;
		}

		return true;

	}

	private boolean keyupChecker(Event e) {
		int stroke = e.getKeyCode();

		searchFieldScale();

		switch (stroke) {
		case 8: // backspace
			if (isMultiple && backstrokeLength < 1 && choices > 0) {
				keydownBackstroke();
			} else if (pendingBackstroke == null) {
				// resultClearHighlight();
				// resultsSearch();
			}
			break;

		case 13: // enter
			e.preventDefault();
			sendNewValueEvent();
			return false;
		case 27: // escape
			if (resultsShowing) {
				resultsHide();
			}
			return false;
		case 9:
		case 38:
		case 40:
		case 16:
		case 91:
		case 17:
			// do nothing
			break;
		default:
			// resultsSearch();
			break;
		}

		return true;
	}

	private void sendNewValueEvent() {

		String val = searchField.val();
		searchField.val("");
		fireEvent(new NewValueEvent(this, val));

	}

	private boolean maxSelectedOptionsReached() {
		return options.getMaxSelectedOptions() != -1 && options.getMaxSelectedOptions() <= choices;
	}

	private void resultActivate(GQuery query) {
		query.addClass(css.activeResult());

	}

	private SafeHtml resultAddGroup(GroupItem group) {
		if (!group.isDisabled()) {
			group.domId = containerId + "_g_" + group.getArrayIndex();
			return ChozenTemplate.templates.group(group.domId, css.groupResult(), group.getLabel());
		} else {
			return null;
		}
	}

	private SafeHtml resultAddOption(OptionItem option) {
		if (!option.isDisabled()) {
			option.domId = containerId + "_o_" + option.getArrayIndex();

			StringBuilder classes = new StringBuilder();

			if (!(option.isSelected() && isMultiple)) {
				classes.append(css.activeResult()).append(" ");
			}

			if (option.isSelected()) {
				classes.append(css.resultSelected()).append(" ");
			}

			if (option.getGroupArrayIndex() != -1) {
				classes.append(css.groupOption()).append(" ");
			}

			if (option.getClasses() != null) {
				classes.append(option.getClasses());
			}

			String style = option.getStyle();

			return ChozenTemplate.templates.option(option.getDomId(), classes.toString().trim(), style, option.getText());
		}
		return null;
	}

	private void resultClearHighlight() {
		if (resultHighlight != null) {
			resultHighlight.removeClass(css.highlighted());
			resultHighlight = null;
		}

	}

	private void resultDeactivate(GQuery query) {
		query.removeClass(css.activeResult());

	}

	private void resultDeselect(int index) {
		OptionItem item = (OptionItem) selectItems.get(index);

		item.setSelected(false);

		// select option in original element
		OptionElement option = selectElement.getOptions().getItem(item.getOptionsIndex());
		option.setSelected(false);

		$("#" + containerId + "_o_" + index).removeClass(css.resultSelected()).addClass(css.activeResult()).show();

		resultClearHighlight();
		winnowResults();

		fireEvent(new FreeTextChosenChangeEvent(option.getValue(), false, this));

		searchFieldScale();
	}

	private void resultDoHighlight(GQuery el) {
		if (el == null || el.length() == 0) {
			return;
		}

		resultClearHighlight();

		resultHighlight = el;

		resultHighlight.addClass(css.highlighted());

		int maxHeight = (int) searchResults.cur("maxHeight", true);
		int visibleTop = searchResults.scrollTop();
		int visibleBottom = maxHeight + visibleTop;

		int highTop = resultHighlight.position().top + searchResults.scrollTop();
		int highBottom = highTop + resultHighlight.outerHeight();

		if (highBottom >= visibleBottom) {
			int toScroll = highBottom - maxHeight;
			searchResults.scrollTop(toScroll > 0 ? toScroll : 0);
		} else if (highTop < visibleTop) {
			searchResults.scrollTop(highTop);
		}

	}

	private void resultsBuild() {

		selectItems = new SelectParser().parse(selectElement);

		if (isMultiple && choices > 0) {
			searchChoices.find("li." + css.searchChoice()).remove();
			choices = 0;
		} else if (!isMultiple) {
			selectedItem.addClass(css.chznDefault()).find("span").text(defaultText);

			if (selectElement.getOptions().getLength() <= options.getDisableSearchThreshold()) {
				container.addClass(css.chznContainerSingleNoSearch());
			} else {
				container.removeClass(css.chznContainerSingleNoSearch());
			}
		}

		SafeHtmlBuilder content = new SafeHtmlBuilder();

		for (int i = 0; i < selectItems.length(); i++) {
			SelectItem item = selectItems.get(i);

			if (item.isGroup()) {
				SafeHtml result = resultAddGroup((GroupItem) item);
				if (result != null) {
					content.append(result);
				}
			} else {
				OptionItem optionItem = (OptionItem) item;

				if (optionItem.isEmpty()) {
					continue;
				}

				SafeHtml optionHtml = resultAddOption(optionItem);
				if (optionHtml != null) {
					content.append(optionHtml);
				}

				if (optionItem.isSelected() && isMultiple) {
					choiceBuild(optionItem);
				} else if (optionItem.isSelected() && !isMultiple) {
					selectedItem.removeClass(css.chznDefault()).find("span").text(optionItem.getText());
					if (allowSingleDeselect) {
						singleDeselectControlBuild();
					}
				}
			}
		}
		searchFieldDisabled();
		showSearchFieldDefault();
		searchFieldScale();

		searchResults.html(content.toSafeHtml().asString());

	}

	private void resultSelect(Event e) {
		if (resultHighlight != null) {
			GQuery high = resultHighlight;
			String highId = high.attr("id");

			resultClearHighlight();

			if (isMultiple) {
				resultDeactivate(high);
			} else {
				searchResults.find("." + css.resultSelected()).removeClass(css.resultSelected());
				resultSingleSelected = high;
				selectedItem.removeClass(css.chznDefault());
			}

			high.addClass(css.resultSelected());

			int position = Integer.parseInt(highId.substring(highId.lastIndexOf("_") + 1));
			OptionItem item = (OptionItem) selectItems.get(position);
			item.setSelected(true);
			selectElement.getOptions().getItem(item.getOptionsIndex()).setSelected(true);

			if (isMultiple) {
				choiceBuild(item);
			} else {
				selectedItem.find("span").text(item.getText());
				if (allowSingleDeselect) {
					singleDeselectControlBuild();
				}
			}

			if (!e.getMetaKey() || !isMultiple) {
				resultsHide();
			}

			searchField.val("");

			if (isMultiple || currentValue == null || !currentValue.equals($selectElement.val())) {
				String value = selectElement.getOptions().getItem(item.getOptionsIndex()).getValue();
				fireEvent(new FreeTextChosenChangeEvent(value, this));
			}

			currentValue = $selectElement.val();

			searchFieldScale();
		}

	}

	private void resultsHide() {
		if (!resultsShowing) {
			return;
		}

		if (!isMultiple) {
			selectedItem.removeClass(css.chznSingleWithDrop());
		}

		resultClearHighlight();

		fireEvent(new HidingDropDownEvent(this));

		dropdown.css("left", "-9000px");
		resultsShowing = false;

	}

	private void resultsReset(Event e) {
		OptionElement firstoption = selectElement.getOptions().getItem(0);
		if (firstoption != null) {
			firstoption.setSelected(true);
		}

		selectedItem.find("span").text(defaultText);
		if (!isMultiple) {
			selectedItem.addClass(css.chznDefault());
		}

		showSearchFieldDefault();
		resultsResetCleanup();

		fireEvent(new FreeTextChosenChangeEvent(null, this));
		if (activeField) {
			resultsHide();
		}

	}

	private void resultsResetCleanup() {
		selectedItem.find("abbr").remove();
	}

	private void resultsSearch() {
		if (resultsShowing) {
			winnowResults();
		} else {
			resultsShow();
		}

	}

	private boolean resultsShow() {
		// if (!isMultiple) {
		// selectedItem.addClass(css.chznSingleWithDrop());
		// if (resultSingleSelected != null) {
		// resultDoHighlight(resultSingleSelected);
		// }
		// } else if (maxSelectedOptionsReached()) {
		// fireEvent(new MaxSelectedEvent(this));
		// return false;
		// }
		//
		// int ddTop = isMultiple ? container.height() : container.height() - 1;
		//
		// fireEvent(new ShowingDropDownEvent(this));
		//
		// dropdown.css("top", ddTop + "px").css("left", "0");
		// resultsShowing = true;
		//
		// searchField.focus();
		// searchField.val(searchField.val());
		//
		// winnowResults();

		return true;
	}

	private void resultsToggle() {
		if (resultsShowing) {
			resultsHide();
		} else {
			resultsShow();
		}
	}

	private void searchFieldDisabled() {
		isDisabled = selectElement.isDisabled();
		if (isDisabled) {
			container.addClass(css.chznDisabled());
			InputElement.as(searchField.get(0)).setDisabled(true);
			if (!isMultiple) {
				selectedItem.unbind("focus", activateAction);
			}

		} else {
			container.removeClass(css.chznDisabled());
			InputElement.as(searchField.get(0)).setDisabled(false);
			if (!isMultiple) {
				selectedItem.bind("focus", activateAction);
			}

		}
	}

	private void searchFieldScale() {
		if (!isMultiple) {
			return;
		}

		StringBuilder styleBlock = new StringBuilder("position:absolute; left: -1000px; top: -1000px; visibility:hidden;");
		String[] styleToCopy = { "font-size", "font-style", "font-weight", "font-family", "line-height", "text-transform", "letter-spacing" };

		for (String style : styleToCopy) {
			styleBlock.append(style).append(':').append(searchField.css(style));
		}

		GQuery div = $("<div />").attr("style", styleBlock.toString()).text(searchField.val());
		$("body").append(div);

		int w = div.width() + 25;
		div.remove();

		if (w > fWidth - 10) {
			w = fWidth - 10;
		}

		searchField.css("width", w + "px");

		int ddTop = container.height();
		dropdown.css("top", ddTop + "px");
	}

	private boolean searchResultsMouseOut(Event e) {
		Element targetEl = e.getEventTarget().cast();
		GQuery $e = $(targetEl);

		if ($e.hasClass(css.activeResult()) || $e.parents("." + css.activeResult()).length() > 0) {
			resultClearHighlight();
		}

		return false;
	}

	private boolean searchResultsMouseOver(Event e) {
		Element targetEl = e.getEventTarget().cast();
		GQuery $e = $(targetEl);

		GQuery target = $e.hasClass(css.activeResult()) ? $e : $e.parents("." + css.activeResult()).first();
		if (!target.isEmpty()) {
			resultDoHighlight(target);
		}

		return false;
	}

	// private boolean searchResultsMouseUp(Event e) {
	// Element targetEvent = e.getEventTarget().cast();
	// GQuery $e = $(targetEvent);
	//
	// GQuery target =
	// $e.hasClass(css.activeResult()) ? $e : $e.parents("." +
	// css.activeResult()).first();
	// if (!target.isEmpty()) {
	// resultHighlight = target;
	// resultSelect(e);
	// }
	// return false;
	// }

	private void setDefaultText() {

		String dataPlaceHolder = selectElement.getAttribute("data-placeholder");

		if (dataPlaceHolder != null && dataPlaceHolder.length() > 0) {
			defaultText = dataPlaceHolder;
		} else if (isMultiple) {
			if (options.getPlaceholderTextMultiple() != null) {
				defaultText = options.getPlaceholderTextMultiple();
			} else if (options.getPlaceholderText() != null) {
				defaultText = options.getPlaceholderText();
			} else {
				defaultText = "Select Some Options";
			}
		} else {
			if (options.getPlaceholderTextSingle() != null) {
				defaultText = options.getPlaceholderTextSingle();
			} else if (options.getPlaceholderText() != null) {
				defaultText = options.getPlaceholderText();
			} else {
				defaultText = "Select an Option";
			}
		}

		String dataNoResultsText = selectElement.getAttribute("data-no_results_text");
		if (dataNoResultsText != null && dataNoResultsText.length() > 0) {
			resultsNoneFound = dataNoResultsText;
		} else if (options.getNoResultsText() != null) {
			resultsNoneFound = options.getNoResultsText();
		} else {
			resultsNoneFound = "No results match";
		}

	}

	private void setDefaultValues() {
		clickTestAction = new Function() {
			@Override
			public boolean f(Event e) {
				return testActiveClick(e);
			}
		};

		activateAction = new Function() {
			@Override
			public boolean f(Event e) {
				return activateField(e);
			}
		};

		activeField = false;
		mouseOnContainer = false;
		resultsShowing = false;

		NodeList<OptionElement> optionsList = selectElement.getOptions();
		allowSingleDeselect = options.isAllowSingleDeselect() && optionsList.getLength() > 0 && "".equals(optionsList.getItem(0).getText());

		choices = 0;

		if (options.getResources() != null) {
			css = options.getResources().css();
		} else {
			css = GWT.<Resources> create(Resources.class).css();
		}

		// Force the injection the first time only
		// If you want to use different css file for different GwtChosen
		// component
		// please register your css files (css.ensureInject()) before the first
		// call of the plugin
		if (!cssInjected) {
			StyleInjector.inject(css.getText(), true);
			cssInjected = true;
		}

	}

	private void setTabIndex() {
		String tabIndexProperty = $selectElement.attr(TABINDEX_PROPERTY);
		if (tabIndexProperty != null && tabIndexProperty.length() > 0) {
			$selectElement.attr(TABINDEX_PROPERTY, -1);

			if (isMultiple) {
				searchField.attr(TABINDEX_PROPERTY, tabIndexProperty);
			} else {
				selectedItem.attr(TABINDEX_PROPERTY, tabIndexProperty);
				searchField.attr(TABINDEX_PROPERTY, -1);
			}
		}

	}

	private void setup() {
		boolean isHidden = false;
		containerId = buildContainerId();
		fWidth = $selectElement.outerWidth();

		// Temporary fix. IIf the select element is inside a hidden container
		// GQuery cannot get the size of the select element.
		if (fWidth == 0) {
			$("body").append("<div id='gwt_chosen_temp_div' style='display:block;position:absolute;left:-9000px; visibility:hidden'> </div>");
			GQuery tempDiv = $("#gwt_chosen_temp_div");
			tempDiv.append($selectElement.clone());

			fWidth = tempDiv.children("select").outerWidth();

			tempDiv.remove();
			isHidden = fWidth > 0;

		}

		isRTL = LocaleInfo.getCurrentLocale().isRTL();
		String cssClasses = isRTL ? css.chznContainer() + " " + css.chznRtl() : css.chznContainer();

		GQuery containerTemp = $(ChozenTemplate.templates.container(containerId, cssClasses).asString()).width(fWidth);

		if (isMultiple) {
			containerTemp.html(ChozenTemplate.templates.contentMultiple(css.chznChoices(), css.searchField(), defaultText, css.defaultClass(), css.chznDrop(), css.chznResults()).asString());
		} else {
			containerTemp.html(ChozenTemplate.templates.contentSingle(css.chznSingle(), css.chznDefault(), defaultText, css.chznDrop(), css.chznSearch(), css.chznResults()).asString());
		}

		// insert container after the select elements
		$selectElement.hide().after(containerTemp);
		container = $("#" + containerId);
		container.addClass(isMultiple ? css.chznContainerMulti() : css.chznContainerSingle());

		dropdown = container.find("div." + css.chznDrop()).first();
		int ddTop = container.height();
		int ddWidth = fWidth - getSideBorderPadding(dropdown, isHidden);
		dropdown.css(Properties.create("{\"width\": " + ddWidth + "px, \"top\": " + ddTop + "px}"));

		searchField = container.find("input").first();
		searchResults = container.find("ul." + css.chznResults()).first();
		searchFieldScale();

		if (isMultiple) {
			searchChoices = container.find("ul." + css.chznChoices()).first();
			searchContainer = container.find("li." + css.searchField()).first();
		} else {
			searchContainer = container.find("div." + css.chznSearch()).first();
			selectedItem = container.find("." + css.chznSingle()).first();
			int searchFieldWidth = ddWidth - getSideBorderPadding(searchContainer, isHidden) - getSideBorderPadding(searchField, isHidden);
			searchField.css("width", searchFieldWidth + "px");
		}

		resultsBuild();

		setTabIndex();

		fireEvent(new ReadyEvent(this));

	}

	private void showSearchFieldDefault() {
		if (isMultiple && choices < 1 && !activeField) {
			searchField.val(defaultText);
			searchField.addClass(css.defaultClass());
		} else {
			searchField.val("");
			searchField.removeClass(css.defaultClass());
		}
	}

	private void singleDeselectControlBuild() {
		if (allowSingleDeselect && selectedItem.find("abbr").isEmpty()) {
			selectedItem.find("span").first().after("<abbr class=\"" + css.searchChoiceClose() + "\"></abbr>");
		}

	}

	private boolean testActiveClick(Event e) {

		Element target = e.getEventTarget().cast();
		GQuery $e = $(target);

		if ($e.parents("#" + containerId).length() > 0) {
			activeField = true;
		} else {
			closeField();
		}
		return false;
	}

	private void winnowResults() {

	}

	private void winnowResultsClear() {
		searchField.val("");
		GQuery lis = searchResults.find("li");

		for (Element li : lis.elements()) {
			GQuery $li = $(li);
			if ($li.hasClass(css.groupResult())) {
				$li.css("display", "");
			} else if (!isMultiple || !$li.hasClass(css.resultSelected())) {
				resultActivate($li);
			}
		}

	}

}
